<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="zh-cn">
<head>
  <meta charset="UTF-8" />
  <title>文件传输 Demo - Flask + Socket.IO + Layui</title>
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <!-- Layui 样式 -->
  <link rel="stylesheet" href="https://unpkg.com/layui@2.8.18/dist/css/layui.css"/>
  <style>
    body { padding: 16px; }
    .file-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
    .file-name { flex: 1; min-width: 160px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
    .progress-wrap { margin-top: 8px; }
    .table-wrap { margin-top: 16px; }
  </style>
</head>
<body>

<div class="layui-card">
  <div class="layui-card-header">文件上传</div>
  <div class="layui-card-body">
    <div class="file-row">
      <input type="file" id="fileInput" multiple class="layui-input" />
      <button class="layui-btn" id="btnUpload">开始上传</button>
      <button class="layui-btn layui-btn-primary" id="btnRefresh">刷新列表</button>
    </div>

    <div id="uploadProgress" class="progress-wrap"></div>
  </div>
</div>

<div class="layui-card table-wrap">
  <div class="layui-card-header">文件列表</div>
  <div class="layui-card-body">
    <table class="layui-table">
      <thead>
        <tr>
          <th>文件名</th>
          <th style="width:140px;">大小</th>
          <th style="width:180px;">最后修改</th>
          <th style="width:120px;">操作</th>
        </tr>
      </thead>
      <tbody id="filesTbody">
        <tr><td colspan="4" style="text-align:center;">加载中...</td></tr>
      </tbody>
    </table>
  </div>
</div>

<!-- Socket.IO 客户端（由 Flask-SocketIO 提供 /socket.io/socket.io.js） -->
<!-- <script src="/socket.io/socket.io.js"></script> -->
<!-- Layui -->
<script src="https://unpkg.com/layui@2.8.18/dist/layui.js"></script>
    <script src="https://cdn.socket.io/4.7.4/socket.io.min.js"></script>

<script>
  // Layui 模块
  layui.use(['element', 'layer'], function() {
    const element = layui.element;
    const layer = layui.layer;

    // 连接 Socket.IO
    const socket = io();

    // 存储每个上传任务的信息：uploadId -> {name, size}
    const uploadMap = {};

    // 元素引用
    const fileInput = document.getElementById('fileInput');
    const btnUpload = document.getElementById('btnUpload');
    const btnRefresh = document.getElementById('btnRefresh');
    const filesTbody = document.getElementById('filesTbody');
    const progressWrap = document.getElementById('uploadProgress');

    // 工具函数
    function fmtSize(bytes) {
      if (bytes == null) return '-';
      const units = ['B','KB','MB','GB','TB'];
      let i = 0, num = bytes;
      while (num >= 1024 && i < units.length - 1) {
        num /= 1024; i++;
      }
      return num.toFixed(num >= 10 || i === 0 ? 0 : 1) + ' ' + units[i];
    }

    function renderFiles(files) {
      if (!files || files.length === 0) {
        filesTbody.innerHTML = '<tr><td colspan="4" style="text-align:center;">暂无文件</td></tr>';
        return;
      }
      filesTbody.innerHTML = files.map(f => {
        const enc = encodeURIComponent(f.name);
        return `
          <tr>
            <td title="${f.name}">${f.name}</td>
            <td>${fmtSize(f.size)}</td>
            <td>${f.mtime}</td>
            <td>
              <a class="layui-btn layui-btn-sm" href="/download/${enc}">下载</a>
            </td>
          </tr>
        `;
      }).join('');
    }

    async function fetchFiles() {
      try {
        const res = await fetch('/files');
        const data = await res.json();
        if (data.ok) renderFiles(data.files);
        else layer.msg('获取文件列表失败');
      } catch (e) {
        console.error(e);
        layer.msg('获取文件列表异常');
      }
    }

    function createProgressBar(uploadId, filename) {
      const filter = `prog_${uploadId}`;
      const item = document.createElement('div');
      item.style.marginBottom = '10px';
      item.innerHTML = `
        <div class="file-name">${filename}</div>
        <div class="layui-progress" lay-showPercent="true" lay-filter="${filter}">
          <div class="layui-progress-bar" lay-percent="0%"></div>
        </div>
      `;
      progressWrap.appendChild(item);
      element.render('progress');
      return filter;
    }

    function updateProgress(filter, percent) {
      // percent: 0~100
      element.progress(filter, Math.min(100, Math.max(0, percent)) + '%');
    }

    function uuid() {
      // 简单 UUID（仅用于区分上传任务）
      return 'xxxxxxxyxxxx'.replace(/[xy]/g, c => {
        const r = Math.random() * 16 | 0;
        const v = c === 'x' ? r : (r & 0x3 | 0x8);
        return v.toString(16);
      });
    }

    // 上传逻辑：使用原生 fetch + FormData，这样能附加 sid、upload_id 等信息
    async function uploadFiles() {
      const files = fileInput.files;
      if (!files || files.length === 0) {
        layer.msg('请先选择文件');
        return;
      }
      if (!socket.id) {
        layer.msg('Socket 未连接，稍后再试');
        return;
      }

      for (const file of files) {
        const uploadId = uuid();
        uploadMap[uploadId] = { name: file.name, size: file.size };
        const filter = createProgressBar(uploadId, file.name);

        const form = new FormData();
        form.append('file', file, file.name);
        form.append('sid', socket.id);
        form.append('upload_id', uploadId);
        form.append('filename', file.name);
        form.append('filesize', String(file.size));

        try {
          const res = await fetch('/upload', {
            method: 'POST',
            body: form
          });
          const data = await res.json();
          if (!data.ok) {
            layer.msg(`上传失败：${file.name}`);
            updateProgress(filter, 0);
          } else {
            updateProgress(filter, 100);
          }
        } catch (e) {
          console.error(e);
          layer.msg(`上传异常：${file.name}`);
          updateProgress(filter, 0);
        }
      }
    }

    // 进度事件监听（服务端推送）
    socket.on('upload_progress', (payload) => {
      const { upload_id, bytes, total } = payload;
      const info = uploadMap[upload_id];
      if (!info) return;
      const fileTotal = total && total > 0 ? total : info.size;
      const percent = fileTotal > 0 ? Math.floor(bytes / fileTotal * 100) : 0;
      updateProgress(`prog_${upload_id}`, percent);
    });

    socket.on('upload_error', (payload) => {
      const { upload_id, error } = payload || {};
      const info = uploadMap[upload_id];
      if (info) {
        layer.msg(`上传失败：${info.name}，错误：${error || '未知'}`);
      }
    });

    // 文件列表更新（所有客户端都会收到）
    socket.on('file_list_updated', (payload) => {
      if (payload && payload.files) {
        renderFiles(payload.files);
      } else {
        fetchFiles();
      }
    });

    // 事件绑定
    btnUpload.addEventListener('click', uploadFiles);
    btnRefresh.addEventListener('click', fetchFiles);

    // 初次加载列表
    fetchFiles();
  });
</script>

</body>
</html>